namespace Hack.Decompiling.PEA.PE
{
  using System;
  using System.IO;
  using System.Collections.Generic;
  using System.Text;

  #region Enumerations
  public enum PEType : ushort
  {
    Unknown = 0x00,
    /// <summary>
    /// The file is an executable image. 
    /// This value is defined as IMAGE_NT_OPTIONAL_HDR32_MAGIC in a 32-bit application 
    /// and as IMAGE_NT_OPTIONAL_HDR64_MAGIC in a 64-bit application.
    /// </summary>
    PE32  = 0x10b,
    PE64  = 0x20b,
    ROM   = 0x107,
  }

  public enum SubSystem:ushort
  {
    UNKNOWN                 = 0,   // Unknown subsystem.
    NATIVE                  = 1,   // Image doesn't require a subsystem.
    WINDOWS_GUI             = 2,   // Image runs in the Windows GUI subsystem.
    WINDOWS_CUI             = 3,   // Image runs in the Windows character subsystem.
    OS2_CUI                 = 5,   // image runs in the OS/2 character subsystem.
    POSIX_CUI               = 7,   // image runs in the Posix character subsystem.
    NATIVE_WINDOWS          = 8,   // image is a native Win9x driver.
    WINDOWS_CE_GUI          = 9,   // Image runs in the Windows CE subsystem.
    EFI_APPLICATION         = 10,  //
    EFI_BOOT_SERVICE_DRIVER = 11,  //
    EFI_RUNTIME_DRIVER      = 12,  //
    EFI_ROM                 = 13,
    XBOX                    = 14,

  }
  [Flags]
  public enum DllAttributes:ushort
  {
    UNKNOWN                 = 0x0000,
    NO_ISOLATION            = 0x0200,    // Image understands isolation and doesn't want it
    NO_SEH                  = 0x0400,    // Image does not use SEH.  No SE handler may reside in this image
    NO_BIND                 = 0x0800,    // Do not bind this image.
    //                        0x1000     // Reserved.
    WDM_DRIVER              = 0x2000,    // Driver uses WDM model
    //                        0x4000     // Reserved.
    TERMINAL_SERVER_AWARE   = 0x8000,
  }

  #endregion

  public unsafe struct OptionalHeader32 : OptionalHeader //224 Bytes
  {
    #region Statics
    public static OptionalHeader32 it()
    {
      return new OptionalHeader32();
    }
    public static OptionalHeader32 from(BinaryReader reader)
    {
      OptionalHeader32 header = it();
      header.Read(reader);
      return header;
    }
    #endregion
    #region Consts
    public const int SizeOfOptionalHeader = 224;
    public const int NumberOfDirectoryEntries = 16;
    #endregion
    #region Fields
    internal UInt16 _Magic;
    internal Byte   _MajorLinkerVersion;
    internal Byte   _MinorLinkerVersion;
    internal UInt32 _SizeOfCode;
    internal UInt32 _SizeOfInitializedData;
    internal UInt32 _SizeOfUninitializedData;
    internal UInt32 _AddressOfEntryPoint;
    internal UInt32 _BaseOfCode;
    internal UInt32 _BaseOfData;
    internal UInt32 _ImageBase;
    internal UInt32 _SectionAlignment;
    internal UInt32 _FileAlignment;
    internal UInt16 _MajorOperatingSystemVersion;
    internal UInt16 _MinorOperatingSystemVersion;
    internal UInt16 _MajorImageVersion;
    internal UInt16 _MinorImageVersion;
    internal UInt16 _MajorSubsystemVersion;
    internal UInt16 _MinorSubsystemVersion;
    internal UInt32 _Win32VersionValue;
    internal UInt32 _SizeOfImage;
    internal UInt32 _SizeOfHeaders;
    internal UInt32 _CheckSum;
    internal UInt16 _Subsystem;
    internal UInt16 _DllCharacteristics;
    internal UInt32 _SizeOfStackReserve;
    internal UInt32 _SizeOfStackCommit;
    internal UInt32 _SizeOfHeapReserve;
    internal UInt32 _SizeOfHeapCommit;
    internal UInt32 _LoaderFlags;
    internal UInt32 _NumberOfRvaAndSizes;

    internal fixed UInt64 _DataDirectory[NumberOfDirectoryEntries];
    #endregion
    #region Properties
    public UInt16 HeaderSize
    {
      get
      {
        return SizeOfOptionalHeader + NumberOfDirectoryEntries * 8;
      }
    }
    public UInt16 Magic
    {
      get { return _Magic; }
      set { _Magic = value; }
    }
    public Byte MajorLinkerVersion
    {
      get { return _MajorLinkerVersion; }
      set { _MajorLinkerVersion = value; }
    }
    public Byte MinorLinkerVersion
    {
      get { return _MinorLinkerVersion; }
      set { _MinorLinkerVersion = value; }
    }
    public UInt32 SizeOfCode
    {
      get { return _SizeOfCode; }
      set { _SizeOfCode = value; }
    }
    public UInt32 SizeOfInitializedData
    {
      get { return _SizeOfInitializedData; }
      set { _SizeOfInitializedData = value; }
    }
    public UInt32 SizeOfUninitializedData
    {
      get { return _SizeOfUninitializedData; }
      set { _SizeOfUninitializedData = value; }
    }
    public UInt32 AddressOfEntryPoint
    {
      get { return _AddressOfEntryPoint; }
      set { _AddressOfEntryPoint = value; }
    }
    public UInt32 BaseOfCode
    {
      get { return _BaseOfCode; }
      set { _BaseOfCode = value; }
    }
    public UInt32 BaseOfData
    {
      get { return _BaseOfData; }
      set { _BaseOfData = value; }
    }
    public UInt64 ImageBase
    {
      get { return _ImageBase; }
      set { _ImageBase = (UInt32)value; }
    }
    public UInt32 SectionAlignment
    {
      get { return _SectionAlignment; }
      set { _SectionAlignment = value; }
    }
    public UInt32 FileAlignment
    {
      get { return _FileAlignment; }
      set { _FileAlignment = value; }
    }
    public UInt16 MajorOperatingSystemVersion
    {
      get { return _MajorOperatingSystemVersion; }
      set { _MajorOperatingSystemVersion = value; }
    }
    public UInt16 MinorOperatingSystemVersion
    {
      get { return _MinorOperatingSystemVersion; }
      set { _MinorOperatingSystemVersion = value; }
    }
    public UInt16 MajorImageVersion
    {
      get { return _MajorImageVersion; }
      set { _MajorImageVersion = value; }
    }
    public UInt16 MinorImageVersion
    {
      get { return _MinorImageVersion; }
      set { _MinorImageVersion = value; }
    }
    public UInt16 MajorSubsystemVersion
    {
      get { return _MajorSubsystemVersion; }
      set { _MajorSubsystemVersion = value; }
    }
    public UInt16 MinorSubsystemVersion
    {
      get { return _MinorSubsystemVersion; }
      set { _MinorSubsystemVersion = value; }
    }
    public UInt32 Win32VersionValue
    {
      get { return _Win32VersionValue; }
      set { _Win32VersionValue = value; }
    }
    public UInt32 SizeOfImage
    {
      get { return _SizeOfImage; }
      set { _SizeOfImage = value; }
    }
    public UInt32 SizeOfHeaders
    {
      get { return _SizeOfHeaders; }
      set { _SizeOfHeaders = value; }
    }
    public UInt32 CheckSum
    {
      get { return _CheckSum; }
      set { _CheckSum = value; }
    }
    public UInt16 SubSystem
    {
      get { return _Subsystem; }
      set { _Subsystem = value; }
    }
    public UInt16 DllCharacteristics
    {
      get { return _DllCharacteristics; }
      set { _DllCharacteristics = value; }
    }
    public UInt64 SizeOfStackReserve
    {
      get { return _SizeOfStackReserve; }
      set { _SizeOfStackReserve = (UInt32)value; }
    }
    public UInt64 SizeOfStackCommit
    {
      get { return _SizeOfStackCommit; }
      set { _SizeOfStackCommit = (UInt32)value; }
    }
    public UInt64 SizeOfHeapReserve
    {
      get { return _SizeOfHeapReserve; }
      set { _SizeOfHeapReserve = (UInt32)value; }
    }
    public UInt64 SizeOfHeapCommit
    {
      get { return _SizeOfHeapCommit; }
      set { _SizeOfHeapCommit = (UInt32)value; }
    }
    public UInt32 LoaderFlags
    {
      get { return _LoaderFlags; }
      set { _LoaderFlags = value; }
    }
    public UInt32 NumberOfRvaAndSizes
    {
      get { return _NumberOfRvaAndSizes < NumberOfDirectoryEntries ? 
        _NumberOfRvaAndSizes = NumberOfDirectoryEntries : 
        _NumberOfRvaAndSizes; 
      }
      set { _NumberOfRvaAndSizes = value < NumberOfDirectoryEntries ?
        _NumberOfRvaAndSizes = NumberOfDirectoryEntries:
        value; 
      }
    }
    public PEType PEType
    {
      get
      {
        return (PEType)this._Magic;
      }
      set
      {
        this._Magic = (UInt16)value;
      }
    }
    public SubSystem SubSystemAttribute
    {
      get
      {
        return (SubSystem)this._Subsystem;
      }
      set
      {
        this._Subsystem = (UInt16)value;
      }
    }
    public DllAttributes DllAttributes
    {
      get
      {
        return (DllAttributes)this._DllCharacteristics;
      }
      set
      {
        this._DllCharacteristics = (UInt16)value;
      }
    }
    public UInt32 NumberOfDirectories
    {
      get
      {
        return NumberOfDirectoryEntries;
      }
    }

    public ImageDataDirectory this[DirectoryEntries entry]
    {
      get
      {
        return this[(UInt32)entry];
      }
      set
      {
        this[(UInt32)entry] = value;
      }
    }
    public ImageDataDirectory this[UInt32 index]
    {
      get
      {
        fixed (UInt64* directories = this._DataDirectory)
        {
          return index >= 0 && index < NumberOfRvaAndSizes ?
            ImageDataDirectory.of(directories[index]) :
            ImageDataDirectory.it();
        }
      }
      set
      {
        fixed (UInt64* directories = this._DataDirectory)
        {
          if (index >= 0 && index < NumberOfRvaAndSizes)
          {
            directories[index] = value;
          }
        }
      }
    }
    #endregion
    #region Constructors
    #endregion
    #region Methods
    public OptionalHeader Read(BinaryReader reader)
    {
      if (reader != null)
      {
        this._Magic = Utils.Read16(reader);
        this._MajorLinkerVersion = Utils.Read8(reader);
        this._MinorLinkerVersion = Utils.Read8(reader);
        this._SizeOfCode = Utils.Read32(reader);
        this._SizeOfInitializedData = Utils.Read32(reader);
        this._SizeOfUninitializedData = Utils.Read32(reader);
        this._AddressOfEntryPoint = Utils.Read32(reader);
        this._BaseOfCode = Utils.Read32(reader);
        this._BaseOfData = Utils.Read32(reader);
        this._ImageBase = Utils.Read32(reader);
        this._SectionAlignment = Utils.Read32(reader);
        this._FileAlignment = Utils.Read32(reader);
        this._MajorOperatingSystemVersion = Utils.Read16(reader);
        this._MinorOperatingSystemVersion = Utils.Read16(reader);
        this._MajorImageVersion = Utils.Read16(reader);
        this._MinorImageVersion = Utils.Read16(reader);
        this._MajorSubsystemVersion = Utils.Read16(reader);
        this._MinorSubsystemVersion = Utils.Read16(reader);
        this._Win32VersionValue = Utils.Read32(reader);
        this._SizeOfImage = Utils.Read32(reader);
        this._SizeOfHeaders = Utils.Read32(reader);
        this._CheckSum = Utils.Read32(reader);
        this._Subsystem = Utils.Read16(reader);
        this._DllCharacteristics = Utils.Read16(reader);
        this._SizeOfStackReserve = Utils.Read32(reader);
        this._SizeOfStackCommit = Utils.Read32(reader);
        this._SizeOfHeapReserve = Utils.Read32(reader);
        this._SizeOfHeapCommit = Utils.Read32(reader);
        this._LoaderFlags = Utils.Read32(reader);
        this._NumberOfRvaAndSizes = Utils.Read32(reader);
        fixed (UInt64* directories = this._DataDirectory)
        {
          Utils.Read64(reader, directories, NumberOfRvaAndSizes);
        }
      }
      return this;
    }
    public OptionalHeader Write(BinaryWriter writer)
    {
      if (writer != null)
      {
        Utils.Write16(writer,this._Magic);
        Utils.Write8(writer, this._MajorLinkerVersion);
        Utils.Write8(writer, this._MinorLinkerVersion);
        Utils.Write32(writer, this._SizeOfCode);
        Utils.Write32(writer, this._SizeOfInitializedData);
        Utils.Write32(writer, this._SizeOfUninitializedData);
        Utils.Write32(writer, this._AddressOfEntryPoint);
        Utils.Write32(writer, this._BaseOfCode);
        Utils.Write32(writer, this._BaseOfData);
        Utils.Write32(writer, this._ImageBase);
        Utils.Write32(writer, this._SectionAlignment);
        Utils.Write32(writer, this._FileAlignment);
        Utils.Write16(writer, this._MajorOperatingSystemVersion);
        Utils.Write16(writer, this._MinorOperatingSystemVersion);
        Utils.Write16(writer, this._MajorImageVersion);
        Utils.Write16(writer, this._MinorImageVersion);
        Utils.Write16(writer, this._MajorSubsystemVersion);
        Utils.Write16(writer, this._MinorSubsystemVersion);
        Utils.Write32(writer, this._Win32VersionValue);
        Utils.Write32(writer, this._SizeOfImage);
        Utils.Write32(writer, this._SizeOfHeaders);
        Utils.Write32(writer, this._CheckSum);
        Utils.Write16(writer, this._Subsystem);
        Utils.Write16(writer, this._DllCharacteristics);
        Utils.Write32(writer, this._SizeOfStackReserve);
        Utils.Write32(writer, this._SizeOfStackCommit);
        Utils.Write32(writer, this._SizeOfHeapReserve);
        Utils.Write32(writer, this._SizeOfHeapCommit);
        Utils.Write32(writer, this._LoaderFlags);
        Utils.Write32(writer, this._NumberOfRvaAndSizes);
        fixed (UInt64* directories = this._DataDirectory)
        {
          Utils.Write64(writer, directories, NumberOfRvaAndSizes);
        }
      }
      return this;
    }

    public override string ToString()
    {
      StringBuilder builder = new StringBuilder();
      builder.AppendFormat("Magic:                          {0:X4}\r\n", this.Magic);
      builder.AppendFormat("MajorLinkerVersion:             {0:X2}\r\n", this.MajorLinkerVersion);
      builder.AppendFormat("MinorLinkerVersion:             {0:X2}\r\n", this.MinorLinkerVersion);
      builder.AppendFormat("SizeOfCode:                     {0:X8}\r\n", this.SizeOfCode);
      builder.AppendFormat("SizeOfInitializedData:          {0:X8}\r\n", this.SizeOfInitializedData);
      builder.AppendFormat("SizeOfUninitializedData:        {0:X8}\r\n", this.SizeOfUninitializedData);
      builder.AppendFormat("AddressOfEntryPoint:            {0:X8}\r\n", this.AddressOfEntryPoint);
      builder.AppendFormat("BaseOfCode:                     {0:X8}\r\n", this.BaseOfCode);
      builder.AppendFormat("BaseOfData:                     {0:X8}\r\n", this.BaseOfData);
      builder.AppendFormat("ImageBase:                      {0:X8}\r\n", this.ImageBase);
      builder.AppendFormat("SectionAlignment:               {0:X8}\r\n", this.SectionAlignment);
      builder.AppendFormat("FileAlignment:                  {0:X8}\r\n", this.FileAlignment);
      builder.AppendFormat("MajorOperatingSystemVersion:    {0:X4}\r\n", this.MajorOperatingSystemVersion);
      builder.AppendFormat("MinorOperatingSystemVersion:    {0:X4}\r\n", this.MinorOperatingSystemVersion);
      builder.AppendFormat("MajorImageVersion:              {0:X4}\r\n", this.MajorImageVersion);
      builder.AppendFormat("MinorImageVersion:              {0:X4}\r\n", this.MinorImageVersion);
      builder.AppendFormat("MajorSubsystemVersion:          {0:X4}\r\n", this.MajorSubsystemVersion);
      builder.AppendFormat("MinorSubsystemVersion:          {0:X4}\r\n", this.MinorSubsystemVersion);
      builder.AppendFormat("Win32VersionValue:              {0:X8}\r\n", this.Win32VersionValue);
      builder.AppendFormat("SizeOfImage:                    {0:X8}\r\n", this.SizeOfImage);
      builder.AppendFormat("SizeOfHeaders:                  {0:X8}\r\n", this.SizeOfHeaders);
      builder.AppendFormat("CheckSum:                       {0:X8}\r\n", this.CheckSum);
      builder.AppendFormat("Subsystem:                      {0:X4}\r\n", this.SubSystem);
      builder.AppendFormat("DllCharacteristics:             {0:X4}\r\n", this.DllCharacteristics);
      builder.AppendFormat("SizeOfStackReserve:             {0:X8}\r\n", this.SizeOfStackReserve);
      builder.AppendFormat("SizeOfStackCommit:              {0:X8}\r\n", this.SizeOfStackCommit);
      builder.AppendFormat("SizeOfHeapReserve:              {0:X8}\r\n", this.SizeOfHeapReserve);
      builder.AppendFormat("SizeOfHeapCommit:               {0:X8}\r\n", this.SizeOfHeapCommit);
      builder.AppendFormat("LoaderFlags:                    {0:X8}\r\n", this.LoaderFlags);
      builder.AppendFormat("NumberOfRvaAndSizes:            {0:X8}\r\n", this.NumberOfRvaAndSizes);

      builder.AppendFormat("PEType:                         {0}\r\n", this.PEType);
      builder.AppendFormat("Subsystem:                      {0}\r\n", this.SubSystem);
      builder.AppendFormat("DllAttributes:                  {0}\r\n", this.DllAttributes);

      this.FormatDirectories(builder);
     
      return builder.ToString();
    }

    internal StringBuilder FormatDirectories(StringBuilder builder)
    {
      if (builder != null)
      {
        for (UInt32 i = 0; i < this.NumberOfRvaAndSizes; i++)
        {
          ImageDataDirectory directory = this[i];

          builder.AppendFormat("{0,16}:   RVA={1:X8},Size={2:X8}\r\n",
            (DirectoryEntries)i,
            directory.VirtualAddress ,
            directory.VirtualSize
            );
        }
      }
      return builder;
    }
    #endregion
  }
}
